import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, filedialog
import subprocess
import threading
import time
import os
import queue
import re
import sys
import traceback
import random
import shutil
import configparser  # 新增导入

# ====================== 工具类 ======================
class ToolTip:
    """创建工具提示框，当鼠标悬停在控件上时显示提示信息"""
    def __init__(self, widget, text):
        self.widget = widget
        self.text = text
        self.tip_window = None
        self.widget.bind("<Enter>", self.show_tip)
        self.widget.bind("<Leave>", self.hide_tip)
    
    def show_tip(self, event=None):
        """显示工具提示"""
        if self.tip_window or not self.text:
            return
        x, y, _, _ = self.widget.bbox("insert")
        x = x + self.widget.winfo_rootx() + 25
        y = y + self.widget.winfo_rooty() + 25
        
        self.tip_window = tk.Toplevel(self.widget)
        self.tip_window.wm_overrideredirect(True)
        self.tip_window.wm_geometry(f"+{x}+{y}")
        
        label = tk.Label(self.tip_window, text=self.text, justify="left",
                         background="#ffffe0", relief="solid", borderwidth=1,
                         font=("tahoma", "8", "normal"))
        label.pack(ipadx=1)
    
    def hide_tip(self, event=None):
        """隐藏工具提示"""
        if self.tip_window:
            self.tip_window.destroy()
        self.tip_window = None

# ====================== 文件管理类 ======================
class FileManager:
    """Manage files on the iQue card"""
    def __init__(self):
        self.device_files = []
        self.file_sizes = {}
    
    def parse_file_list(self, log_text):
        """Parse file list from aulon2 output"""
        self.device_files = []
        self.file_sizes = {}
        
        # Improved file pattern matching: filename: size [unit]
        pattern = r"([\w\.]+):\s+(\d+)\s+([KM]?i?B)"
        matches = re.findall(pattern, log_text, re.IGNORECASE)
        
        for match in matches:
            filename = match[0]
            size_val = int(match[1])
            unit = match[2].lower()
            
            # Convert size to bytes
            if unit == "kib":
                size_bytes = size_val * 1024
            elif unit == "mib":
                size_bytes = size_val * 1024 * 1024
            else:  # Includes B and unknown units
                size_bytes = size_val
            
            self.device_files.append(filename)
            self.file_sizes[filename] = self.format_size(size_bytes)
        
        # Alternative parsing: filename: size
        if not matches:
            pattern = r"([\w\.]+):\s+(\d+)"
            matches = re.findall(pattern, log_text, re.IGNORECASE)
            for match in matches:
                filename = match[0]
                size_val = int(match[1])
                # Assume default unit is KiB
                size_bytes = size_val * 1024
                self.device_files.append(filename)
                self.file_sizes[filename] = self.format_size(size_bytes)
        
        return len(self.device_files)
    
    def format_size(self, size_bytes):
        """Convert byte size to human-readable format"""
        if size_bytes < 1024:
            return f"{size_bytes} B"
        elif size_bytes < 1024 * 1024:
            return f"{size_bytes / 1024:.2f} KB"
        else:
            return f"{size_bytes / (1024 * 1024):.2f} MB"
    
    def get_file_type(self, filename):
        """Get file type"""
        ext = os.path.splitext(filename)[1].lower()
        if ext == ".app":
            return "Game File"
        elif ext == ".rec":
            return "Encrypted File"
        elif ext == ".sys":
            return "System File"
        elif ext == ".sta":
            return "Status File"
        elif ext in [".u00", ".u01"]:
            return "User Data File"
        elif ext == ".bnd":
            return "Binding File"
        elif ext == ".db":
            return "Database File"
        elif ext == ".pak":
            return "Resource File"
        return "Unknown File"

# ====================== GUI主类 ======================
class Aulon2GUI:
    """Main GUI class for iQue Card Manager"""
    LANGUAGE_TEXTS = {
        "title": "神游卡管理器 v0.06 by sksaUltra64",
        "connect": "连接设备",
        "browse": "浏览",
        "inject": "注入",
        "dump": "转储",
        "delete": "移除",
        "operations": "Device Operations",
        "filelist": "File List",
        "console": "Console",
        "status_ready": "● Not Connected",
        "status_connecting": "● Connecting...",
        "status_connected": "● Connected",
        "status_failed": "● Connection Failed",
        "status_lost": "● Connection Lost",
        "ready": "Ready - Connect device to view files",
        "please_wait": "Please wait...",
        "confirm_delete": "Confirm Delete",
        "delete_prompt": "Delete this file?\n\nFile: {}",
        "warning_connect": "Please connect the device first",
        "warning_select": "Please select a valid file",
        "file_loading": "Loading file list...",
        "no_files": "No files found. Raw output:",
        "file_count": "Found {} files",
        "log_count": "Parsed {} lines",
        "file_details": "File Details",
        "file_name": "File: {}",
        "file_size": "Size: {}",
        "file_type": "Type: {}",
        "app_type": "Game File",
        "rec_type": "Encrypted File",
        "status_refreshing": "Refreshing...",
        "status_refreshed": "Refresh completed",
        "status_deleting": "Deleting {}...",
        "status_deleted": "{} deleted successfully",
        "status_delete_failed": "{} deletion failed - see console",
        "injection": "File Injection",
        "select_file": "Select File:",
        "inject_btn": "Inject File",
        "inject_confirm": "Confirm Injection",
        "inject_prompt": "Inject this file to device?\n\nFile: {}\n\n",
        "injecting": "Injecting {}...",
        "inject_success": "{} injected successfully",
        "inject_failed": "{} injection failed",
        "inject_progress": "Progress: {}%",
        "inject_progress_unknown": "Injecting...",
        "inject_file_not_found": "File not found: {}\nPlease place file in program directory",
        "inject_waiting": "Injection may take several minutes...",
        "progress_indeterminate": "Processing...",
        "inject_disabled": "Injection disabled",
        "retry_count": "Retrying ({}/{})",
        "storage_ready": "Waiting for data...",
        "dump_file": "Dump File",
        "dump_prompt": "Dump this file?\n\nFile: {}",
        "dumping": "Dumping {}...",
        "dump_success": "{} dumped successfully",
        "dump_failed": "{} dump failed",
        "file_operations": "File Operations",
        # 新增CFW相关文本
        "cfw_button": "CFW",
        "cfw_tooltip": "替换固件 (nand-cfw.bin和spare-cfw.bin)",
        "cfw_confirm": "确认CFW固件替换",
        "cfw_prompt": "此操作将替换设备固件为CFW版本!\n\n请确保:\n1. 已备份原始固件\n2. nand-cfw.bin和spare-cfw.bin文件已放在aulon2目录\n3. 设备连接稳定\n\n确定要继续吗?",
        "cfw_in_progress": "CFW固件替换中...",
        "cfw_success": "CFW固件替换成功",
        "cfw_failed": "CFW固件替换失败",
        "cfw_files_missing": "CFW文件未找到!\n\n请确保以下文件存在于aulon2目录:\n- nand-cfw.bin\n- spare-cfw.bin"
    }
    
    def __init__(self, root):
        self.root = root
        self.root.title(self.LANGUAGE_TEXTS["title"])
        self.root.geometry("900x700")
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        
        # Get executable directory (for packaged app)
        if getattr(sys, 'frozen', False):
            self.base_dir = os.path.dirname(sys.executable)
        else:
            self.base_dir = os.path.dirname(os.path.abspath(__file__))
        
        # 添加配置文件路径
        self.config_file = os.path.join(self.base_dir, 'config.ini')
        
        # Initialize state variables
        self.process = None
        self.output_queue = queue.Queue()
        self.running = True
        self.refreshing = False
        self.current_operation = None
        self.temp_file_to_delete = None
        self.last_command = None
        self.last_command_success = {}
        self.storage_capacity = "Unknown"
        self.selected_file = None
        
        # Temporary variables for storage capacity parsing
        self.free_value = None
        self.free_unit = None
        self.used_value = None
        self.used_unit = None
        
        # Unit conversion coefficients (KiB/MiB/GiB to MB)
        self.unit_conversion = {
            "KiB": 1/1024,   # KiB to MB
            "MiB": 1,         # MiB to MB
            "GiB": 1024       # GiB to MB
        }
        
        # Setup file manager
        self.file_manager = FileManager()
        
        # 尝试从配置文件加载 aulon2 路径
        self.aulon2_path = self.load_config()
        
        # 如果配置文件中没有保存路径，则让用户选择
        if not self.aulon2_path or not os.path.exists(self.aulon2_path):
            self.check_aulon2_exists()
        else:
            self.aulon2_dir = os.path.dirname(self.aulon2_path)
            self.log_message(f"Loaded aulon2.exe path from config: {self.aulon2_path}", "info")
        
        # Create UI
        self.create_widgets()
        
        # Start output monitoring thread
        self.output_thread = threading.Thread(target=self.monitor_output, daemon=True)
        self.output_thread.start()
        
        self.log_message("Program started", "info")
        self.log_message(f"Program directory: {self.base_dir}", "info")
        self.log_message(f"aulon2.exe path: {self.aulon2_path}", "info")
    
    def load_config(self):
        """从配置文件加载 aulon2.exe 路径"""
        config = configparser.ConfigParser()
        if os.path.exists(self.config_file):
            try:
                config.read(self.config_file, encoding='utf-8')
                if 'Paths' in config and 'aulon2_path' in config['Paths']:
                    return config['Paths']['aulon2_path']
            except Exception as e:
                self.log_message(f"Error reading config: {str(e)}", "error")
        return None
    
    def save_config(self, path):
        """保存 aulon2.exe 路径到配置文件"""
        config = configparser.ConfigParser()
        config['Paths'] = {'aulon2_path': path}
        
        try:
            with open(self.config_file, 'w', encoding='utf-8') as configfile:
                config.write(configfile)
            self.log_message(f"Saved aulon2.exe path to config: {path}", "info")
            return True
        except Exception as e:
            self.log_message(f"Error saving config: {str(e)}", "error")
            return False
    
    # ====================== 日志和文件管理 ======================
    def check_aulon2_exists(self):
        """Check if aulon2.exe exists - supports two directory structures"""
        try:
            # Attempt 1: Same directory
            aulon2_same_dir = os.path.join(self.base_dir, 'aulon2.exe')
            if os.path.exists(aulon2_same_dir):
                self.aulon2_dir = self.base_dir
                self.aulon2_path = aulon2_same_dir
                self.save_config(self.aulon2_path)  # 保存到配置文件
                return
            
            # Attempt 2: aulon2 subdirectory
            aulon2_sub_dir = os.path.join(self.base_dir, 'aulon2', 'aulon2.exe')
            if os.path.exists(aulon2_sub_dir):
                self.aulon2_dir = os.path.join(self.base_dir, 'aulon2')
                self.aulon2_path = aulon2_sub_dir
                self.save_config(self.aulon2_path)  # 保存到配置文件
                return
            
            # Attempt 3: Let user manually select
            self.log_message("aulon2.exe not found, please select manually", "warning")
            file_path = filedialog.askopenfilename(
                title="Locate aulon2.exe",
                filetypes=[("Executable", "*.exe"), ("All files", "*.*")]
            )
            
            if file_path and os.path.isfile(file_path):
                self.aulon2_dir = os.path.dirname(file_path)
                self.aulon2_path = file_path
                self.save_config(self.aulon2_path)  # 保存到配置文件
                return
            
            # All attempts failed
            error_msg = (
                "aulon2.exe not found!\n"
                "Please ensure it's in one of these locations:\n"
                f"1. Same directory as program: {self.base_dir}\n"
                f"2. aulon2 subdirectory: {os.path.join(self.base_dir, 'aulon2')}\n"
                "3. Or manually select location"
            )
            messagebox.showerror("Critical Error", error_msg)
            self.root.destroy()
            raise FileNotFoundError("aulon2.exe not found")
        
        except Exception as e:
            messagebox.showerror("Initialization Error", f"Initialization failed: {str(e)}")
            self.root.destroy()
    
    def log_message(self, message, level="info"):
        """Log message to console only"""
        try:
            self.console.config(state='normal')
            self.console.insert(tk.END, f"[{time.strftime('%H:%M:%S')}] {message}\n")
            self.console.see(tk.END)
            self.console.config(state='disabled')
        except Exception:
            pass
    
    # ====================== UI创建 ======================
    def create_widgets(self):
        """Create program interface"""
        try:
            # Main frame
            main_frame = ttk.Frame(self.root)
            main_frame.pack(fill="both", expand=True, padx=10, pady=10)
            
            # Left panel - device operations
            left_frame = ttk.LabelFrame(main_frame, text=self.LANGUAGE_TEXTS["operations"], width=250)
            left_frame.pack(side="left", fill="y", padx=5, pady=5)
            left_frame.pack_propagate(False)
            
            # Operation buttons
            self.create_operation_buttons(left_frame)
            
            # Right panel - file list
            file_list_frame = ttk.LabelFrame(main_frame, text=self.LANGUAGE_TEXTS["filelist"])
            file_list_frame.pack(side="right", fill="both", expand=True, padx=5, pady=5)
            
            # File list
            self.create_file_list(file_list_frame)
            
            # Console
            console_frame = ttk.LabelFrame(self.root, text=self.LANGUAGE_TEXTS["console"])
            console_frame.pack(fill="both", expand=True, padx=10, pady=5)
            self.console = scrolledtext.ScrolledText(console_frame, wrap=tk.WORD, state='disabled', height=8, font=("Consolas", 9))
            self.console.pack(fill="both", expand=True, padx=5, pady=5)
            
            # Status bar
            self.create_status_bar()
            
            # Progress bar
            self.create_progress_bar()
            
        except Exception as e:
            self.log_message(f"UI initialization failed: {str(e)}", "error")
            self.log_message(traceback.format_exc(), "error")
            messagebox.showerror("UI Error", f"Interface initialization failed: {str(e)}")
            self.root.destroy()
    
    def create_operation_buttons(self, parent):
        """Create operation buttons area"""
        btn_frame = ttk.Frame(parent)
        btn_frame.pack(fill="x", padx=5, pady=10)
        
        self.connect_btn = ttk.Button(
            btn_frame, 
            text=self.LANGUAGE_TEXTS["connect"], 
            command=self.connect_device,
            width=15
        )
        self.connect_btn.pack(side="top", fill="x", padx=10, pady=5)
        
        # Status indicator
        status_indicator_frame = ttk.Frame(parent)
        status_indicator_frame.pack(fill="x", padx=5, pady=10)
        
        ttk.Label(status_indicator_frame, text="Status:").pack(side="left", padx=5)
        self.connection_status = ttk.Label(
            status_indicator_frame, 
            text=self.LANGUAGE_TEXTS["status_ready"], 
            foreground="red",
            font=("Arial", 9, "bold"),
            width=10
        )
        self.connection_status.pack(side="right", padx=5)
        
        # Storage capacity display
        storage_frame = ttk.Frame(parent)
        storage_frame.pack(fill="x", padx=5, pady=5)
        
        ttk.Label(storage_frame, text="神游卡", font=("Arial", 9)).pack(side="left", padx=(0, 5))
        self.storage_label = ttk.Label(
            storage_frame,
            text=self.LANGUAGE_TEXTS["storage_ready"],
            font=("Arial", 9)
        )
        self.storage_label.pack(side="left", padx=0)
        
        # 添加CFW按钮
        cfw_frame = ttk.Frame(parent)
        cfw_frame.pack(fill="x", padx=5, pady=5)
        
        self.cfw_btn = ttk.Button(
            cfw_frame,
            text=self.LANGUAGE_TEXTS["cfw_button"],
            command=self.replace_cfw,
            width=15
        )
        self.cfw_btn.pack(side="top", fill="x", padx=10, pady=5)
        # 添加工具提示
        ToolTip(self.cfw_btn, self.LANGUAGE_TEXTS["cfw_tooltip"])
        
        # 添加一个填充框架，使按钮能够下移
        spacer_frame = ttk.Frame(parent)
        spacer_frame.pack(side="top", fill="both", expand=True)
        
        # 将浏览和注入按钮移到底部框架中
        bottom_ops_frame = ttk.Frame(parent)
        bottom_ops_frame.pack(side="bottom", fill="x", padx=5, pady=10)
        
        self.browse_btn = ttk.Button(
            bottom_ops_frame, 
            text=self.LANGUAGE_TEXTS["browse"], 
            command=self.browse_file,
            width=15
        )
        self.browse_btn.pack(side="top", fill="x", padx=10, pady=5)
        
        self.inject_btn = ttk.Button(
            bottom_ops_frame, 
            text=self.LANGUAGE_TEXTS["inject"], 
            command=self.inject_file,
            width=15
        )
        self.inject_btn.pack(side="top", fill="x", padx=10, pady=5)
    
    def create_file_list(self, parent):
        """创建文件列表区域 - 显示文件名和大小"""
        # 创建Treeview - 两列：文件名和大小
        self.tree = ttk.Treeview(parent, columns=("filename", "size"), show="headings")
        
        # 设置列
        self.tree.heading("filename", text="File Name")
        self.tree.heading("size", text="Size")
        
        # 设置列宽
        self.tree.column("filename", width=400, anchor="w")
        self.tree.column("size", width=100, anchor="center")
        
        # 添加滚动条
        scrollbar = ttk.Scrollbar(parent, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        
        # 绑定选择事件
        self.tree.bind("<<TreeviewSelect>>", self.on_file_select)
        
        # 布局
        self.tree.pack(side="top", fill="both", expand=True, padx=5, pady=5)
        scrollbar.pack(side="right", fill="y")
        
        # Selected file display and operation buttons below file list
        bottom_frame = ttk.Frame(parent)
        bottom_frame.pack(side="bottom", fill="x", padx=5, pady=5)
        
        # Selected file display
        selected_frame = ttk.Frame(bottom_frame)
        selected_frame.pack(fill="x", padx=5, pady=5)
        
        ttk.Label(selected_frame, text="Selected:").pack(side="left", padx=(0, 5))
        self.selected_label = ttk.Label(
            selected_frame,
            text="None",
            font=("Arial", 9),
            foreground="blue"
        )
        self.selected_label.pack(side="left", padx=0)
        
        # File operation buttons - dump and delete
        file_ops_frame = ttk.Frame(bottom_frame)
        file_ops_frame.pack(fill="x", padx=5, pady=5)
        
        self.dump_btn = ttk.Button(
            file_ops_frame, 
            text=self.LANGUAGE_TEXTS["dump"], 
            command=self.dump_file,
            width=8
        )
        self.dump_btn.pack(side="left", padx=5, pady=5)
        
        self.delete_btn = ttk.Button(
            file_ops_frame, 
            text=self.LANGUAGE_TEXTS["delete"], 
            command=self.delete_file,
            width=8
        )
        self.delete_btn.pack(side="right", padx=5, pady=5)
    
    def create_status_bar(self):
        """Create status bar"""
        # Create status bar frame
        self.status_frame = ttk.Frame(self.root, relief="sunken", padding=(2, 0))
        self.status_frame.pack(side="bottom", fill="x", padx=0, pady=0)
        
        # Create status variable and label
        self.status_var = tk.StringVar()
        self.status_var.set(self.LANGUAGE_TEXTS["ready"])
        status_label = ttk.Label(self.status_frame, textvariable=self.status_var, anchor="w")
        status_label.pack(side="left", fill="x", expand=True)
    
    def create_progress_bar(self):
        """Create progress bar area (hidden by default)"""
        # Create progress bar frame
        self.progress_frame = ttk.Frame(self.root, padding=(5, 0))
        # Hide by default, show when needed
        self.progress_frame.pack_forget()
        
        # Create progress label
        self.progress_label = ttk.Label(
            self.progress_frame, 
            text=self.LANGUAGE_TEXTS["ready"],
            anchor="w"
        )
        self.progress_label.pack(fill="x", padx=5, pady=(5,0))
        
        # Create progress bar
        self.progress = ttk.Progressbar(
            self.progress_frame, 
            orient="horizontal", 
            length=100, 
            mode="determinate"
        )
        self.progress.pack(fill="x", padx=5, pady=(0,5))
    
    # ====================== 文件列表管理 ======================
    def update_file_list(self):
        """更新文件列表显示 - 显示文件名和大小"""
        # 清除现有列表
        for item in self.tree.get_children():
            self.tree.delete(item)
        
        # 添加新文件 - 显示文件名和大小
        for filename in self.file_manager.device_files:
            # 只显示.app和.rec文件的大小
            ext = os.path.splitext(filename)[1].lower()
            if ext in [".app", ".rec"]:
                size = self.file_manager.file_sizes.get(filename, "N/A")
            else:
                size = ""
            
            self.tree.insert("", "end", values=(filename, size))
    
    def on_file_select(self, event):
        """When user selects a file"""
        selected_items = self.tree.selection()
        if selected_items:
            item = selected_items[0]
            # Get first column value (filename)
            self.selected_file = self.tree.item(item, "values")[0]
            self.selected_label.config(text=self.selected_file)
        else:
            self.selected_file = None
            self.selected_label.config(text="None")
    
    # ====================== 设备操作 ======================
    def connect_device(self):
        """Connect to iQue device"""
        if self.process and self.process.poll() is None:
            self.log_message("Already connected to device", "info")
            return
        
        self.log_message("Connecting to iQue device...", "info")
        self.connection_status.config(text=self.LANGUAGE_TEXTS["status_connecting"], foreground="blue")
        
        try:
            self.process = subprocess.Popen(
                [self.aulon2_path],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                bufsize=1,
                encoding='utf-8',
                creationflags=subprocess.CREATE_NO_WINDOW,
                cwd=self.aulon2_dir
            )
            
            self.last_command = "B"
            self.last_command_success["B"] = False
            self.process.stdin.write("B\n")
            self.process.stdin.flush()
            
            threading.Timer(5.0, self.check_connect_timeout).start()
            
        except Exception as e:
            self.log_message(f"Connection failed: {str(e)}", "error")
            self.log_message(traceback.format_exc(), "error")
            self.connection_status.config(text=self.LANGUAGE_TEXTS["status_failed"], foreground="red")
    
    def check_connect_timeout(self):
        """Check connection timeout"""
        if not self.last_command_success.get("B", False):
            self.log_message("Connection timed out", "error")
            self.connection_status.config(text=self.LANGUAGE_TEXTS["status_failed"], foreground="red")
    
    def on_connect_success(self):
        """Callback when device connects successfully"""
        self.log_message("Device connected successfully!", "info")
        self.connection_status.config(text=self.LANGUAGE_TEXTS["status_connected"], foreground="green")
        self.status_var.set(self.LANGUAGE_TEXTS["ready"])
        self.get_storage_info()
        self.refresh_files()
    
    def get_storage_info(self):
        """Send command C to get storage info"""
        # 关键修复：只有当进程正在运行时才发送命令
        if self.process and self.process.poll() is None:
            self.log_message("Getting storage info...", "info")
            # Reset storage parsing state
            self.free_value = None
            self.free_unit = None
            self.used_value = None
            self.used_unit = None
            self.run_command("C", delay=0.5, expect_success=False)
    
    def update_storage_capacity(self, free, used, unit):
        """Update storage capacity display"""
        try:
            # Ensure values are numeric
            free_value = float(free) * self.unit_conversion.get(unit, 1)
            used_value = float(used) * self.unit_conversion.get(unit, 1)
            
            # Format display
            display_text = f"Free: {free_value:.2f} MB | Used: {used_value:.2f} MB"
            
            self.storage_capacity = display_text
            self.storage_label.config(text=self.storage_capacity)
        except Exception as e:
            self.log_message(f"Storage update failed: {str(e)}", "error")
    
    def run_command(self, command, delay=2, expect_success=True, retries=3):
        """Send command to aulon2 process with retry mechanism"""
        for attempt in range(retries):
            try:
                if self.process is None or self.process.poll() is not None:
                    self.log_message("Process not running, reconnecting", "warning")
                    self.connect_device()
                    time.sleep(1)  # 减少等待时间
                
                if self.process and self.process.poll() is None:
                    self.log_message(f">>> Executing command: {command}", "info")
                    self.process.stdin.write(command + "\n")
                    self.process.stdin.flush()
                    
                    self.last_command = command
                    self.last_command_success[command] = False
                    
                    if expect_success:
                        start_time = time.time()
                        while time.time() - start_time < delay:
                            if self.last_command_success.get(command, False):
                                return True
                            time.sleep(0.1)
                    
                    return True
                
                return False
            except Exception as e:
                self.log_message(f"Command error (attempt {attempt+1}/{retries}): {str(e)}", "error")
                self.connection_status.config(text=self.LANGUAGE_TEXTS["status_lost"])
                
                if attempt < retries - 1:
                    time.sleep(0.5 + random.random() * 0.5)  # 减少重试等待时间
        return False
    
    # ====================== CFW固件替换功能 ======================
    def replace_cfw(self):
        """替换CFW固件"""
        if not self.process or self.process.poll() is not None:
            messagebox.showwarning(self.LANGUAGE_TEXTS["warning_connect"], self.LANGUAGE_TEXTS["warning_connect"])
            return
        
        # 检查CFW文件是否存在
        nand_file = os.path.join(self.aulon2_dir, "nand-cfw.bin")
        spare_file = os.path.join(self.aulon2_dir, "spare-cfw.bin")
        
        if not os.path.exists(nand_file) or not os.path.exists(spare_file):
            messagebox.showerror("文件缺失", self.LANGUAGE_TEXTS["cfw_files_missing"])
            return
        
        # 确认操作
        confirm = messagebox.askyesno(
            self.LANGUAGE_TEXTS["cfw_confirm"], 
            self.LANGUAGE_TEXTS["cfw_prompt"]
        )
        
        if not confirm:
            return
        
        self.log_message("开始CFW固件替换...", "info")
        self.current_operation = "cfw"
        self.status_var.set(self.LANGUAGE_TEXTS["cfw_in_progress"])
        self.connect_btn.config(state="disabled")
        self.cfw_btn.config(state="disabled")
        self.browse_btn.config(state="disabled")
        self.inject_btn.config(state="disabled")
        self.dump_btn.config(state="disabled")
        self.delete_btn.config(state="disabled")
        self.start_indeterminate_progress()
        
        # 执行CFW替换命令
        command = "2 nand-cfw.bin spare-cfw.bin 0-0x40"
        self.log_message(f"执行CFW命令: {command}", "info")
        
        threading.Thread(target=self._replace_cfw_thread, args=(command,), daemon=True).start()
    
    def _replace_cfw_thread(self, command):
        """后台线程执行CFW固件替换"""
        completed = False
        error_message = ""
        try:
            if self.run_command(command, delay=2, retries=3):
                self.log_message("CFW固件替换命令已发送...", "info")
                self.status_var.set("CFW固件替换中...")
                
                start_time = time.time()
                last_output = ""
                timeout = 300  # 5分钟超时
                
                # 使用更智能的结束检测
                end_markers = ["success", "complete", "error", "fail"]
                
                while time.time() - start_time < timeout:
                    if self.process.poll() is not None:
                        break
                    
                    output_lines = []
                    while not self.output_queue.empty():
                        try:
                            line = self.output_queue.get_nowait()
                            output_lines.append(line)
                            last_output += line + "\n"
                            
                            # 检查是否包含结束标记
                            if any(marker in line.lower() for marker in end_markers):
                                if "success" in line.lower() or "complete" in line.lower():
                                    self.log_message("CFW固件替换成功!", "info")
                                    completed = True
                                    break
                                elif "error" in line.lower() or "fail" in line.lower():
                                    self.log_message("CFW固件替换失败", "error")
                                    error_message = line
                                    break
                            
                        except queue.Empty:
                            pass
                    
                    if completed or error_message:
                        break
                    
                    time.sleep(0.5)
                
                if not completed and not error_message:
                    error_message = "CFW固件替换超时"
                    self.log_message(error_message, "error")
            else:
                error_message = "CFW固件替换失败 - 命令未发送"
                self.log_message(error_message, "error")
                
        except Exception as e:
            error_message = f"CFW线程错误: {str(e)}"
            self.log_message(error_message, "error")
        finally:
            if completed:
                self.status_var.set(self.LANGUAGE_TEXTS["cfw_success"])
                time.sleep(1)
            else:
                self.root.after(0, self.stop_progress)
                self.status_var.set(self.LANGUAGE_TEXTS["cfw_failed"])
            
            self.root.after(0, self._enable_buttons)
            self.current_operation = None
            
            # 显示详细错误
            if not completed and error_message:
                self.root.after(0, lambda: messagebox.showerror(
                    "CFW失败",
                    f"CFW固件替换失败\n\n错误详情: {error_message}"
                ))
    
    # ====================== 输出监控 ======================
    def monitor_output(self):
        """Monitor aulon2 process output and detect command responses"""
        try:
            while self.running:
                try:
                    if self.process and self.process.poll() is not None:
                        self.log_message("aulon2 process exited", "warning")
                        self.connection_status.config(text=self.LANGUAGE_TEXTS["status_lost"], foreground="orange")
                        self.process = None
                        time.sleep(1)
                        continue
                    
                    if self.process:
                        output = self.process.stdout.readline()
                        if output:
                            stripped = output.strip()
                            if stripped:
                                self.log_message(stripped, "info")
                                self.output_queue.put(stripped)
                                
                                if "success" in stripped.lower() or "完成" in stripped:
                                    if self.last_command:
                                        self.last_command_success[self.last_command] = True
                                        self.last_command = None
                                
                                if "Init success" in stripped:
                                    self.last_command_success["B"] = True
                                    self.root.after(0, self.on_connect_success)
                                
                                # Storage capacity parsing
                                if stripped.startswith("Free:") or stripped.startswith("Used:"):
                                    # Parse Free line
                                    if stripped.startswith("Free:"):
                                        match = re.search(r"Free:\s+(\d+)\s+\(([\d.]+)\s+(\w+)\)", stripped)
                                        if match:
                                            self.free_value = match.group(2)
                                            self.free_unit = match.group(3)
                                    
                                    # Parse Used line
                                    elif stripped.startswith("Used:"):
                                        match = re.search(r"Used:\s+(\d+)\s+\(([\d.]+)\s+(\w+)\)", stripped)
                                        if match:
                                            self.used_value = match.group(2)
                                            self.used_unit = match.group(3)
                                    
                                    # Update UI when both are parsed
                                    if self.free_value is not None and self.used_value is not None:
                                        # Ensure consistent unit (prefer Free unit)
                                        unit = self.free_unit if self.free_unit else self.used_unit
                                        self.root.after(0, lambda: self.update_storage_capacity(
                                            self.free_value, self.used_value, unit
                                        ))
                                
                                # Check injection progress
                                if self.current_operation == "inject":
                                    if "WriteFile success" in stripped:
                                        self.root.after(0, lambda: self.log_message("Injection successful!", "info"))
                                        self.last_command_success[self.last_command] = True
                                    
                                    elif "error" in stripped.lower() or "fail" in stripped.lower():
                                        self.root.after(0, lambda: self.log_message("Injection failed", "error"))
                                    
                                    progress_match = re.search(r"(\d+)%", stripped)
                                    if progress_match:
                                        progress = int(progress_match.group(1))
                                        self.root.after(0, lambda p=progress: self.update_progress(p))
                    else:
                        time.sleep(0.5)
                except Exception as e:
                    error_msg = f"Output monitoring error: {str(e)}"
                    self.log_message(error_msg, "error")
                    if self.connection_status:
                        self.root.after(0, lambda: self.connection_status.config(
                            text=self.LANGUAGE_TEXTS["status_lost"], foreground="orange"
                        ))
                    time.sleep(0.5)  # 减少错误等待时间
        except Exception as e:
            # Outer exception handling
            error_msg = f"Monitoring thread fatal error: {str(e)}"
            self.log_message(error_msg, "critical")
            # Notify UI thread
            if self.root:
                self.root.after(0, lambda: messagebox.showerror("Thread Crash", "Monitoring thread crashed, program may be unstable."))
    
    # ====================== 进度条管理 ======================
    def update_progress(self, value):
        """Update progress bar display"""
        try:
            self.progress.config(mode="determinate")
            self.progress['value'] = value
            self.progress_label.config(text=self.LANGUAGE_TEXTS["inject_progress"].format(value))
            self.status_var.set(f"Injecting file: {value}%")
        except Exception as e:
            self.log_message(f"Progress bar error: {str(e)}", "error")
    
    def start_indeterminate_progress(self):
        """Start indeterminate progress bar (flow effect)"""
        try:
            self.progress_frame.pack(fill="x", padx=5, pady=5)
            self.progress.config(mode="indeterminate")
            self.progress.start(10)
            self.progress_label.config(text=self.LANGUAGE_TEXTS["progress_indeterminate"])
            self.status_var.set(self.LANGUAGE_TEXTS["please_wait"])
        except Exception as e:
            self.log_message(f"Starting progress flow failed: {str(e)}", "error")
    
    def stop_progress(self):
        """Stop progress bar and reset to ready state, hide progress area"""
        try:
            if self.progress.cget("mode") == "indeterminate":
                self.progress.stop()
            
            self.progress.config(mode="determinate")
            self.progress['value'] = 0
            self.progress_label.config(text=self.LANGUAGE_TEXTS["ready"])
            self.status_var.set(self.LANGUAGE_TEXTS["ready"])
            self.progress_frame.pack_forget()
        except Exception as e:
            self.log_message(f"Stopping progress bar failed: {str(e)}", "error")
    
    # ====================== 文件操作 ======================
    def refresh_files(self):
        """Refresh file list on device using command 5"""
        if not self.process or self.process.poll() is not None:
            messagebox.showwarning(self.LANGUAGE_TEXTS["warning_connect"], self.LANGUAGE_TEXTS["warning_connect"])
            return
        
        if self.refreshing:
            self.log_message("Refresh in progress, please wait...", "info")
            return
            
        self.refreshing = True
        self.current_operation = "refresh"
        self.log_message("Refreshing device file list with command 5...", "info")
        self.status_var.set(self.LANGUAGE_TEXTS["status_refreshing"])
        self.start_indeterminate_progress()
        threading.Thread(target=self._refresh_files_thread, daemon=True).start()
    
    def _refresh_files_thread(self):
        """Background thread for refreshing file list with command 5"""
        try:
            # Clear output queue to start fresh
            while not self.output_queue.empty():
                try:
                    self.output_queue.get_nowait()
                except queue.Empty:
                    break
            
            if self.run_command("5", delay=0.5, expect_success=False):
                self.log_message("File list command sent", "info")
                
                output_lines = []
                start_time = time.time()
                timeout = 3.0  # 减少超时时间
                
                # 使用更智能的结束检测
                end_markers = [">>>", "aulon>", "Game list refresh complete"]
                file_pattern = re.compile(r"([\w\.]+):\s+(\d+)\s+([KM]?i?B)")
                
                while time.time() - start_time < timeout:
                    try:
                        while not self.output_queue.empty():
                            line = self.output_queue.get_nowait()
                            output_lines.append(line)
                            
                            # 检查是否包含结束标记
                            if any(marker in line for marker in end_markers):
                                break
                            
                            # 检查是否包含文件行
                            if file_pattern.search(line):
                                break
                    except queue.Empty:
                        pass
                    
                    # 如果已经检测到文件，提前退出
                    if output_lines and any(".app" in line or ".rec" in line for line in output_lines):
                        break
                    
                    time.sleep(0.1)  # 减少循环等待时间
                
                # 获取剩余输出（如果有）
                try:
                    while not self.output_queue.empty():
                        line = self.output_queue.get_nowait()
                        output_lines.append(line)
                except queue.Empty:
                    pass
                
                raw_output = "\n".join(output_lines)
                
                file_count = self.file_manager.parse_file_list(raw_output)
                
                if file_count > 0:
                    self.log_message(f"Found {file_count} files", "info")
                    self.status_var.set(f"Found {file_count} files")
                    self.root.after(0, self.update_file_list)
                else:
                    self.log_message("No files found in device output", "warning")
                    self.status_var.set("No files found")
                
                # 关键修正：文件列表刷新后立即更新存储信息
                self.get_storage_info()
            else:
                self.log_message("Failed to send file list command", "error")
                self.status_var.set("Refresh failed")
                
        except Exception as e:
            self.log_message(f"Refresh thread error: {str(e)}", "error")
            self.status_var.set("Refresh error")
        finally:
            self.refreshing = False
            self.current_operation = None
            self.root.after(0, self._finish_refresh)
    
    def browse_file(self):
        """Browse local file"""
        file_path = filedialog.askopenfilename(
            initialdir=self.aulon2_dir,
            title=self.LANGUAGE_TEXTS["select_file"],
            filetypes=[
                ("iQue Files", "*.app;*.rec;*.sys;*.sta;*.u00;*.u01;*.bnd;*.db;*.pak"),
                ("All Files", "*.*"),
                ("Application Files", "*.app"),
                ("Encrypted Files", "*.rec"),
                ("System Files", "*.sys")
            ]
        )
        
        if file_path:
            filename = os.path.basename(file_path)
            self.selected_file = filename
            self.selected_label.config(text=filename)
            self.log_message(f"Selected file: {filename}", "info")
            
            # If file not in working directory, copy it
            if os.path.dirname(file_path) != self.aulon2_dir:
                try:
                    dest_path = os.path.join(self.aulon2_dir, filename)
                    if not os.path.exists(dest_path):
                        shutil.copy2(file_path, dest_path)
                    else:
                        self.log_message(f"File already in working directory: {dest_path}", "info")
                except Exception as e:
                    self.log_message(f"File copy failed: {str(e)}", "error")
                    messagebox.showerror("File Error", f"Failed to copy file to working directory:\n{str(e)}")
    
    def inject_file(self):
        """Inject selected file"""
        if not self.selected_file:
            messagebox.showwarning(self.LANGUAGE_TEXTS["warning_select"], self.LANGUAGE_TEXTS["warning_select"])
            return
            
        if not self.process or self.process.poll() is not None:
            messagebox.showwarning(self.LANGUAGE_TEXTS["warning_connect"], self.LANGUAGE_TEXTS["warning_connect"])
            return
        
        file_path = os.path.join(self.aulon2_dir, self.selected_file)
        if not os.path.exists(file_path):
            error_msg = self.LANGUAGE_TEXTS["inject_file_not_found"].format(self.selected_file)
            self.log_message(error_msg, "error")
            messagebox.showerror("File Not Found", error_msg)
            return
            
        confirm = messagebox.askyesno(
            self.LANGUAGE_TEXTS["inject_confirm"], 
            self.LANGUAGE_TEXTS["inject_prompt"].format(self.selected_file) + 
            "\n" + self.LANGUAGE_TEXTS["inject_waiting"]
        )
        
        if not confirm:
            return
        
        self.log_message(f"Starting file injection: {self.selected_file}", "info")
        self.current_operation = "inject"
        self.status_var.set(self.LANGUAGE_TEXTS["injecting"].format(self.selected_file))
        self.connect_btn.config(state="disabled")
        self.cfw_btn.config(state="disabled")
        self.browse_btn.config(state="disabled")
        self.inject_btn.config(state="disabled")
        self.dump_btn.config(state="disabled")
        self.delete_btn.config(state="disabled")
        self.start_indeterminate_progress()
        self.progress['value'] = 0
        self.progress_label.config(text=self.LANGUAGE_TEXTS["inject_progress_unknown"])
        
        command = f"4 {self.selected_file}"
        self.log_message(f"Executing injection command: {command}", "info")
        
        threading.Thread(target=self._inject_file_thread, args=(command, self.selected_file), daemon=True).start()
    
    def _inject_file_thread(self, command, filename):
        """Background thread for file injection"""
        completed = False
        error_message = ""
        try:
            if self.run_command(command, delay=2, retries=3):
                self.log_message(f"{filename} injection started...", "info")
                self.status_var.set(f"{filename} injecting...")
                
                start_time = time.time()
                last_output = ""
                timeout = 150
                
                # 使用更智能的结束检测
                end_markers = ["WriteFile success", "error", "fail"]
                
                while time.time() - start_time < timeout:
                    if self.process.poll() is not None:
                        break
                    
                    output_lines = []
                    while not self.output_queue.empty():
                        try:
                            line = self.output_queue.get_nowait()
                            output_lines.append(line)
                            last_output += line + "\n"
                            
                            # 检查是否包含结束标记
                            if any(marker in line for marker in end_markers):
                                if "WriteFile success" in line:
                                    self.log_message(f"{filename} injected successfully!", "info")
                                    completed = True
                                    break
                                elif "error" in line.lower() or "fail" in line.lower():
                                    self.log_message(f"{filename} injection failed", "error")
                                    break
                            
                            progress_match = re.search(r"(\d+)%", line)
                            if progress_match:
                                progress = int(progress_match.group(1))
                                self.root.after(0, lambda p=progress: self.update_progress(p))
                            
                        except queue.Empty:
                            pass
                    
                    if completed:
                        break
                    
                    time.sleep(0.5)  # 减少等待时间
                
                if not completed:
                    error_message = f"{filename} injection timed out"
                    self.log_message(error_message, "error")
            else:
                error_message = f"{filename} injection failed - command not sent"
                self.log_message(error_message, "error")
                
        except Exception as e:
            error_message = f"Injection thread error: {str(e)}"
            self.log_message(error_message, "error")
        finally:
            if completed:
                time.sleep(1)  # 减少等待时间
                self.root.after(0, self.refresh_files)
            else:
                self.root.after(0, self.stop_progress)
            
            self.root.after(0, self._enable_buttons)
            self.current_operation = None
            
            # Show detailed error
            if not completed and error_message:
                self.root.after(0, lambda: messagebox.showerror(
                    "Injection Failed",
                    f"{filename} injection failed\n\nError details: {error_message}"
                ))
            
            self.status_var.set(f"{filename} injection completed" if completed else f"{filename} injection failed")
    
    def dump_file(self):
        """Dump selected file"""
        if not self.selected_file:
            messagebox.showwarning(self.LANGUAGE_TEXTS["warning_select"], self.LANGUAGE_TEXTS["warning_select"])
            return
            
        if not self.process or self.process.poll() is not None:
            messagebox.showwarning(self.LANGUAGE_TEXTS["warning_connect"], self.LANGUAGE_TEXTS["warning_connect"])
            return
        
        confirm = messagebox.askyesno(
            self.LANGUAGE_TEXTS["dump_file"], 
            self.LANGUAGE_TEXTS["dump_prompt"].format(self.selected_file)
        )
        
        if not confirm:
            return
        
        self.log_message(f"Starting file dump: {self.selected_file}", "info")
        self.status_var.set(self.LANGUAGE_TEXTS["dumping"].format(self.selected_file))
        self.start_indeterminate_progress()
        threading.Thread(target=self._dump_file_thread, args=(self.selected_file,), daemon=True).start()
    
    def _dump_file_thread(self, filename):
        """Background thread for file dump"""
        try:
            if self.run_command(f"3 {filename}", delay=1, retries=3):
                self.log_message(f"{filename} dumped successfully!", "info")
                self.status_var.set(self.LANGUAGE_TEXTS["dump_success"].format(filename))
            else:
                self.log_message(f"{filename} dump failed", "error")
                self.status_var.set(self.LANGUAGE_TEXTS["dump_failed"].format(filename))
                
            time.sleep(0.5)  # 减少等待时间
            self.root.after(0, self.refresh_files)
        except Exception as e:
            self.log_message(f"Dump thread error: {str(e)}", "error")
            self.root.after(0, self._finish_refresh)
    
    def delete_file(self):
        """Delete selected file"""
        if not self.selected_file:
            messagebox.showwarning(self.LANGUAGE_TEXTS["warning_select"], self.LANGUAGE_TEXTS["warning_select"])
            return
            
        if not self.process or self.process.poll() is not None:
            messagebox.showwarning(self.LANGUAGE_TEXTS["warning_connect"], self.LANGUAGE_TEXTS["warning_connect"])
            return
        
        # Fixed bracket issue here
        confirm = messagebox.askyesno(
            self.LANGUAGE_TEXTS["confirm_delete"], 
            self.LANGUAGE_TEXTS["delete_prompt"].format(self.selected_file)
        )
        
        if not confirm:
            return
        
        self.log_message(f"Starting file deletion: {self.selected_file}", "info")
        self.status_var.set(self.LANGUAGE_TEXTS["status_deleting"].format(self.selected_file))
        self.start_indeterminate_progress()
        threading.Thread(target=self._delete_file_thread, args=(self.selected_file,), daemon=True).start()
    
    def _delete_file_thread(self, filename):
        """Background thread for file deletion"""
        try:
            if self.run_command(f"6 {filename}", delay=1, retries=3):
                self.log_message(f"{filename} deleted successfully!", "info")
                self.status_var.set(self.LANGUAGE_TEXTS["status_deleted"].format(filename))
            else:
                self.log_message(f"{filename} deletion failed", "error")
                self.status_var.set(self.LANGUAGE_TEXTS["status_delete_failed"].format(filename))
                
            time.sleep(0.5)  # 减少等待时间
            self.root.after(0, self.refresh_files)
        except Exception as e:
            self.log_message(f"Delete thread error: {str(e)}", "error")
            self.root.after(0, self._finish_refresh)
    
    # ====================== 辅助方法 ======================
    def _finish_refresh(self):
        """Restore UI state after refresh"""
        try:
            self.stop_progress()
        except Exception as e:
            self.log_message(f"Refresh completion error: {str(e)}", "error")
    
    def _enable_buttons(self):
        """Enable all buttons"""
        try:
            self.connect_btn.config(state="normal")
            self.cfw_btn.config(state="normal")
            self.browse_btn.config(state="normal")
            self.inject_btn.config(state="normal")
            self.dump_btn.config(state="normal")
            self.delete_btn.config(state="normal")
            self.current_operation = None
        except Exception as e:
            self.log_message(f"Button state restore error: {str(e)}", "error")
    
    def on_closing(self):
        """Cleanup when closing window"""
        try:
            self.log_message("Program closing...", "info")
            self.running = False  # Notify threads to exit
            
            # Wait for monitoring thread to exit
            if self.output_thread.is_alive():
                self.log_message("Waiting for monitoring thread to exit...", "info")
                self.output_thread.join(1.0)  # 减少等待时间
                
            # Close process
            if self.process and self.process.poll() is None:
                try:
                    self.log_message("Terminating aulon2 process...", "info")
                    self.process.terminate()
                    self.log_message("Disconnected from device", "info")
                    time.sleep(0.2)  # 减少等待时间
                except Exception as e:
                    self.log_message(f"Process termination failed: {str(e)}", "error")
            
            # Cleanup temp files
            if hasattr(self, 'temp_file_to_delete') and self.temp_file_to_delete and os.path.exists(self.temp_file_to_delete):
                try:
                    os.remove(self.temp_file_to_delete)
                except Exception as e:
                    self.log_message(f"Temp file cleanup failed: {str(e)}", "error")
            
        except Exception as e:
            error_msg = f"Shutdown error: {str(e)}"
            self.log_message(error_msg, "error")
        finally:
            self.root.destroy()

# ====================== 异常处理 ======================
def handle_uncaught_exception(exc_type, exc_value, exc_traceback):
    """Handle uncaught exceptions"""
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return
    
    script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
    error_log = os.path.join(script_dir, "crash.log")
    
    with open(error_log, "a", encoding="utf-8") as f:
        f.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Program crashed\n")
        f.write(f"Error type: {exc_type.__name__}\n")
        f.write(f"Error message: {str(exc_value)}\n")
        f.write("-" * 80 + "\n")
    
    try:
        root = tk.Tk()
        root.withdraw()
        messagebox.showerror(
            "Program Crash", 
            f"Unhandled exception occurred:\n{str(exc_value)}\n\n" +
            f"Details saved to:\n{error_log}\n\n" +
            "Please provide this file to the developer for diagnosis."
        )
        root.destroy()
    except:
        print(f"Program crash: {str(exc_value)}")
        print(f"Details saved to: {error_log}")

# ====================== 主程序入口 ======================
if __name__ == "__main__":
    sys.excepthook = handle_uncaught_exception
    
    try:
        root = tk.Tk()
        app = Aulon2GUI(root)
        root.mainloop()
    except Exception as e:
        handle_uncaught_exception(type(e), e, e.__traceback__)